/*
  Copyright 2009 Colin Prinn

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package net.recursv.motific.unittest;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.Modifier;
import javassist.NotFoundException;
import net.recursv.motific.pool.InputPool;
import net.recursv.motific.pool.OutputPool;
import net.recursv.motific.pool.PoolFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Main compiler class!
 */
public class Weaver {
    private static final String CLASS_SUFFIX = ".class";

    private ClassPool _classpathPool;
    private InputPool _inputPool;
    private OutputPool _outputPool;
    static final String JAVA_LANG_OBJECT = "java.lang.Object";
    public static final String TEST_SUPERCLASS = "net.recursv.motific.unittest.TestSuite";
    public static final String TEST_INTERFACE = "net.recursv.motific.unittest.impl.__TestSuite";
    public static final String TEST_SUITE_ITERATOR = "net.recursv.motific.unittest.impl.__TestSuiteIterator";

    /**
     * Creates a new instance
     */
    public Weaver() {
        _classpathPool = ClassPool.getDefault();
    }

    public void setClasspathPool(ClassPool classpathPool) {
        _classpathPool = classpathPool;
    }

    public void execute() throws WeaverException {
        try {
            UnitTestGenerator generator = new UnitTestGenerator();
            List<CtClass> classes = getTestSuiteClasses();
            int testCaseCount = 0;

            for (CtClass aClass : classes) {
                testCaseCount += generator.processClass(aClass);
                _outputPool.addClass(aClass);
            }

            CtClass iteratorClass = _classpathPool.get(TEST_SUITE_ITERATOR);
            generator.processIterator(iteratorClass, classes, testCaseCount);
            _outputPool.addClass(iteratorClass);
        } catch (Throwable e) {
            throw new WeaverException(e);
        }
    }

    /**
     * Returns the class name given a file name.
     *
     * @param fileName the filename
     * @return the class name of the file or null if the filename is not a class file.
     */
    private String getClassName(String fileName) {
        if (fileName.endsWith(CLASS_SUFFIX)) {
            String className = fileName.replace(File.separatorChar, '.');
            return className.substring(0, className.length() - CLASS_SUFFIX.length());
        } else
            return null;
    }

    private List<CtClass> getTestSuiteClasses() throws NotFoundException, IOException {
        List<CtClass> list = new ArrayList<CtClass>();
        String fileName;
        String className;
        for (int i = 0; i < _inputPool.size(); i++) {
            fileName = _inputPool.getFilename(i);
            className = getClassName(fileName);
            if (fileName.endsWith(CLASS_SUFFIX) && isClassValidForProcessing(_classpathPool.get(className))) {
                list.add(_classpathPool.get(className));
            } else {
                _outputPool.addFile(_inputPool.getURL(i), fileName);
            }
        }
        return list;
    }

    private boolean isClassValidForProcessing(CtClass aClass) throws NotFoundException {
        return isTestSuite(aClass) && isUnmodified(aClass) && isConcrete(aClass);
    }

    private boolean isConcrete(CtClass aClass) {
        return !Modifier.isAbstract(aClass.getModifiers()) && !aClass.isInterface();
    }

    public boolean isTestSuite(CtClass aClass) throws NotFoundException {
        return aClass.subclassOf(_classpathPool.get(TEST_SUPERCLASS)) && !aClass.equals(_classpathPool.get(TEST_SUPERCLASS));
    }

    public boolean isUnmodified(CtClass aClass) throws NotFoundException {
        return !aClass.subtypeOf(_classpathPool.get(TEST_INTERFACE));
    }


    /**
     * Sets the classpath.
     *
     * @param classpath the class path
     * @throws Weaver.WeaverException If required classes are not on the class path
     */
    public void setClasspath(String[] classpath) throws WeaverException {
        if (classpath != null && classpath.length > 0)
            try {
                for (String s : classpath)
                    _classpathPool.insertClassPath(s);
            } catch (NotFoundException e) {
                throw new WeaverException(e);
            }
    }

    /**
     * Sets the input file.
     *
     * @param inputFile the input file
     * @throws WeaverException if file is unsupported, the file is corrupt, the classpath is invalid
     */
    public void setInputFile(File inputFile) throws WeaverException {
        try {
            _inputPool = PoolFactory.createInputPool(inputFile);
            _classpathPool.insertClassPath(inputFile.getCanonicalPath());
        } catch (Throwable e) {
            throw new WeaverException(e);
        }
    }

    /**
     * Sets the output file.
     *
     * @param outputFile The output file
     * @throws WeaverException if the file type is unsupported
     */
    public void setOutputFile(File outputFile) throws WeaverException {
        _outputPool = PoolFactory.createOutputPool(outputFile);
    }

    public static class WeaverException extends Exception {
        public WeaverException(String s) {
            super(s);
        }

        public WeaverException(Throwable e) {
            super(e);
        }
    }
}
